package cn.cool.middleware.biz_exception_retry.domain.service;

import cn.cool.middleware.biz_exception_retry.config.BizExceptionRetryConfig;
import cn.cool.middleware.biz_exception_retry.config.interfaces.BizExceptionRetryStrategy;
import cn.cool.middleware.biz_exception_retry.config.registry.BeanMethodRegistry;
import cn.cool.middleware.biz_exception_retry.domain.IBizExceptionRetryRecordService;
import cn.cool.middleware.biz_exception_retry.domain.IBizExceptionRetryTaskService;
import cn.cool.middleware.biz_exception_retry.domain.model.entity.BizExceptionRetryParamEntity;
import cn.cool.middleware.biz_exception_retry.domain.model.valobj.BizExceptionRetryRecordVO;
import cn.cool.middleware.biz_exception_retry.types.common.BizExceptionRetryConstantsVO;
import cn.cool.middleware.biz_exception_retry.types.common.BizExceptionRetryRecordStatusVO;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.ThreadContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
public class BizExceptionRetryTaskService implements ApplicationContextAware, IBizExceptionRetryTaskService, InitializingBean {
    @Resource
    private BizExceptionRetryConfig config;

    @Resource
    private IBizExceptionRetryRecordService bizExceptionRetryRecordService;

    @Resource
    private BeanMethodRegistry beanMethodRegistry;

    @Resource
    private ApplicationContext applicationContext;

    private final Map<String, BizExceptionRetryStrategy> bizExceptionRetryStrategyMap = new ConcurrentHashMap<>();

    // 执行重试任务的线程池
    private static ThreadPoolExecutor pool = null;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 初始化线程池
        pool = new ThreadPoolExecutor(config.getCorePoolSize(),
                config.getMaxPoolSize(),
                config.getKeepAliveTime(),
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(config.getMaxQueueSize()),
                new ThreadFactoryBuilder().setNameFormat("bizExceptionRetryTask-thread-%d").build());
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, BizExceptionRetryStrategy> beansMap = applicationContext.getBeansOfType(BizExceptionRetryStrategy.class);
        bizExceptionRetryStrategyMap.putAll(beansMap);
    }

    @Override // 事务注解
    public void executeBizExceptionRetryTask() {
        final int pageSize = 200;
        Long lastExceptionId = 0L;

        String exceptionModule = config.getExceptionModule();
        while (true) {
            List<BizExceptionRetryRecordVO> bizExceptionRetryRecords = bizExceptionRetryRecordService.listExceptionRetryRecords(lastExceptionId, exceptionModule, pageSize);
            //  如果没有需要重试的业务，那么直接 break 循环即可
            if (CollectionUtil.isEmpty(bizExceptionRetryRecords)) {
                break;
            }

            // 每次获取后更新 lastExceptionId
            lastExceptionId = bizExceptionRetryRecords.get(bizExceptionRetryRecords.size() - 1).getId();

            // 线程池提交 record
            for (BizExceptionRetryRecordVO bizExceptionRetryRecord : bizExceptionRetryRecords) {
                try {
                    pool.execute(() -> retryExceptionTask(bizExceptionRetryRecord));
                } catch (Exception ex) {
                    log.error("[@BizExceptionRetry] 定时任务执行重试任务: {} 异常", bizExceptionRetryRecord, ex);
                    return;
                }
            }
        }
    }

    /**
     * 重试本次分页中的异常事件
     * @param bizExceptionRetryRecord 重试记录
     */
    private void retryExceptionTask(BizExceptionRetryRecordVO bizExceptionRetryRecord) {
        // todo id?
        log.info("[@BizExceptionRetry] 业务异常重试BizExceptionRetry: {}", JSONUtil.toJsonStr(bizExceptionRetryRecord));

        try {
            // 根据异常类型的重试 beanName，获取重试 Bean
            String bizExceptionRetryStrategyBean = bizExceptionRetryRecord.getRetryBeanName();
            BizExceptionRetryStrategy bizExceptionRetryStrategy = null;

            // 如果beanName存在，那么走自定义补偿逻辑
            // 否则，走原有逻辑的调用
            if (StrUtil.isNotBlank(bizExceptionRetryStrategyBean)) {
                bizExceptionRetryStrategy = bizExceptionRetryStrategyMap.get(bizExceptionRetryStrategyBean);
                if (ObjUtil.isNull(bizExceptionRetryStrategy)) {
                    log.error("[@BizExceptionRetry] 异常重试策略类不存在，beanName = {}", bizExceptionRetryStrategyBean);
                    return;
                }
            }

            // 将当前 task 的状态设置为 处理中
            boolean isProcessing = this.setProcessing(bizExceptionRetryRecord.getId(), BizExceptionRetryRecordStatusVO.PROCESSING.getStatus());
            if (!isProcessing) {
                log.info("[@BizExceptionRetry] 任务 [{}] 已经被其他线程处理，无需重复处理.", JSONUtil.toJsonStr(bizExceptionRetryRecord));
                return;
            }

            boolean isSuccess = false;
            BizExceptionRetryParamEntity retryParamEntity = null;
            try {
                retryParamEntity = JSONUtil.toBean(bizExceptionRetryRecord.getRetryParam(), BizExceptionRetryParamEntity.class);
                // 如果有自定义逻辑，则走自定义逻辑
                if (ObjUtil.isNotNull(bizExceptionRetryStrategy)) {
                    bizExceptionRetryStrategy.apply(retryParamEntity);
                } else {
                    // 如果没有自定义逻辑，走原链路调用
                    String beanClassName = bizExceptionRetryRecord.getClassName();
                    String methodSignature = bizExceptionRetryRecord.getMethodSignature();
                    // 获取Bean对象
                    BeanMethodRegistry.MethodInfo methodInfo = beanMethodRegistry.getMethodInfo(beanClassName, methodSignature);
                    Object bean = methodInfo.getBean();
                    Method method = methodInfo.getMethod();
                    // todo 基于结果补偿策略触发
                    method.invoke(bean, retryParamEntity.getRetryParam());
                }

                isSuccess = true;
            } catch (Throwable throwable) {
                log.error("[@BizExceptionRetry] 异常任务重试时异常。exceptionRetry：{}", JSONUtil.toJsonStr(bizExceptionRetryRecord), throwable);
                // todo 编写异常类型描述
            }

            // 修改本次执行记录，将重试次数 + 1,并且更新记录状态
            bizExceptionRetryRecordService.incrBizExceptionRetryDealNumber(bizExceptionRetryRecord.getId());
            int recordRetryStatus = isSuccess ? BizExceptionRetryRecordStatusVO.DEAL_SUCCESS.getStatus() : BizExceptionRetryRecordStatusVO.UN_DEAL.getStatus();
            bizExceptionRetryRecordService.updateBizExceptionRetryRecordStatus(bizExceptionRetryRecord.getId(), recordRetryStatus);

            // 超过最大次数，如果任然重试，打 ERROR 日志
            if (bizExceptionRetryRecord.getDealNum() + 1 >= bizExceptionRetryRecord.getMaxNum()) {
                if (!isSuccess) {
                    log.error("[@BizExceptionRetry] 任务 [{}] 重试超过最大重试次数[{}]次仍失败的异常数据", JSONUtil.toJsonStr(bizExceptionRetryRecord), bizExceptionRetryRecord.getMaxNum());
                }
            }
        } catch (Throwable ex) {
            log.error("[@BizExceptionRetry] 异常重试时，未知错误，exceptionRetry:{}", JSONUtil.toJsonStr(bizExceptionRetryRecord), ex);
        } finally {
            // todo id？
        }
    }


    /**
     * 将重试任务的状态设置为 处理中
     * @param bizExceptionRetryId 重试 id
     * @param status 新状态
     * @return
     */
    private boolean setProcessing(Long bizExceptionRetryId, Integer status) {
        return bizExceptionRetryRecordService.updateRecordStatus2Processing(bizExceptionRetryId, status) == 1;
    }


}
